/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiHTTP.cpp
	A simple system for downloading stuff through the Hypertext Transfer
	Protocol. Conditional RFC 2616 compliance is attempted.

******************************************************************************/

#include "MaxsiLibrary.h"

BeginMaxsiNamespace

#define MAXSI_HTTP_RECEIVE_BUFFER_SIZE 16384

BYTE* CreateHTTPMIMEBodyFileAttachment(char* FileName, BYTE* FileData, size_t FileSize, size_t* ResultSize)
{
	char* ResultHeader = BuildStringA(3,
		"--AaB03x\r\n"
		"Content-Disposition: file; filename=\"", FileName, "\"\r\n"
		"Content-Type: application/octet-stream\r\n"
		"User-Agent: " Maxsi_User_Agent_String "\r\n"
		"\r\n");

	size_t ResultHeaderSize = strlen(ResultHeader);

	if ( ResultHeader == NULL ) { return NULL; }

	char* ResultFooter = "\r\n--AaB03x\r\n";

	size_t ResultFooterSize = strlen(ResultFooter);

	char* Result = new char[ResultHeaderSize + FileSize + ResultFooterSize + 1];

	if ( Result == NULL ) { delete[] ResultHeader; return NULL; }

	memcpy(Result, ResultHeader, ResultHeaderSize);
	memcpy(Result + ResultHeaderSize, FileData, FileSize);
	memcpy(Result + ResultHeaderSize + FileSize, ResultFooter, ResultFooterSize);

	*ResultSize				=	ResultHeaderSize + FileSize + ResultFooterSize;

	// NULL-terminate this for debugging purposes.
	Result[*ResultSize]		=	0;

	return (BYTE*)Result;
}

unsigned int ReadHTTPResponseCode(char* Buffer)
{
	unsigned int Result = (unsigned int)(Buffer[0] - '0') * 100 + (Buffer[1] - '0') * 10 + (Buffer[2] - '0');

	if ( Result > 999 ) { Result = 0; }

	return Result;
}

MaxsiError ParseHTTPURL(char* URL, char** HostName, uint16_t* Port, char** Resource)
{
	MAXSI_TODO("Make this function do stuff properly and according to the official RFC!");

	// Identify if we are dealing with a HTTP URL or not.

	char* HTTPScheme = "http://";

	if ( CaseCompareStringsN(URL, HTTPScheme, strlen(HTTPScheme)) != 0 ) { return MAXSI_ERROR_NOT_SUPPORTED; }

	URL += strlen(HTTPScheme);

	// Isolate the / and/or the :
	size_t NextSlash	=	FindCharA(URL, '/', strlen(URL));
	size_t NextColon	=	FindCharA(URL, ':', strlen(URL));

	// If the colon is found and is before the first slash, we got a port!
	if ( NextColon != SIZE_MAX && NextColon < NextSlash )
	{
		if ( Port != NULL )
		{
			size_t	Available	=	(NextSlash == SIZE_MAX ) ? (strlen(URL) - NextColon) : (NextSlash - NextColon);

			*Port	=	ReadUInt16A10(URL + NextSlash, Available);

			if ( *Port == 0 ) { *Port = 80; }
		}
	}
	else if ( Port != NULL ) { *Port = 80; }

	// Isolate the hostname!
	if ( HostName != NULL )
	{
		size_t HostNameLength = NextSlash;

		if ( NextSlash == SIZE_MAX ) { HostNameLength = strlen(URL); }
		if ( NextColon != SIZE_MAX ) { HostNameLength = NextColon; }

		*HostName	=	SubStringA(0, HostNameLength, URL);
		if ( *HostName == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
	}

	// Isolate the hostname!
	if ( Resource != NULL )
	{

		if ( NextSlash == SIZE_MAX )
		{
			*Resource	=	BuildStringA(1, _MESTR("/"));				
		}
		else
		{
			*Resource	=	SubStringA(NextSlash, strlen(URL + NextSlash), URL);
		}
		if ( *Resource == NULL ) { if ( HostName != NULL && *HostName != NULL ) { delete[] *HostName; } return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	}

	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
//	MaxsiHTTP
//=============================================================================
MAXSI_DEFINE_IMPLEMENTATION(MaxsiHTTP, IHTTP, MAXSI_INTERFACE_DYNAMIC);

MaxsiHTTP::MaxsiHTTP()
{
	Stream					=	NULL;
	Network					=	NULL;
	Major					=	1;
	Minor					=	1;
	Callback				=	NULL;
	HostA					=	NULL;
	HostM					=	NULL;
	Port					=	80;
	Resource				=	NULL;
	RequestHeaders			=	NULL;
	RequestMethod			=	NULL;
	State					=	0;
	TransferEncoding		=	MAXSI_HTTP_TRANSFER_ENCODING_IDENTITY;
	ExpectingBody			=	true;
	MIMEBodyAttachment		=	NULL;
	MIMEBodyAttachmentSize	=	0;
	InterruptType			=	MAXSI_HTTP_INTERRUPT_NONE;
	InterruptData			=	NULL;
	ReceiveBuffer			=	new BYTE[2* MAXSI_HTTP_RECEIVE_BUFFER_SIZE];
	ReceiveBufferSize		=	MAXSI_HTTP_RECEIVE_BUFFER_SIZE;
	KeepBuffer				=	(ReceiveBuffer) ? ReceiveBuffer + MAXSI_HTTP_RECEIVE_BUFFER_SIZE : NULL;
	KeepBufferUsed			=	0;
	KeepBufferSize			=	MAXSI_HTTP_RECEIVE_BUFFER_SIZE;
}

MaxsiHTTP::~MaxsiHTTP()
{
	if ( Network != NULL ) { DeleteInterface(Network); }
	IFDEL(HostA);
	IFDEL(HostM);
	IFDEL(Resource);
	IFDEL(RequestHeaders);
	IFDEL(RequestMethod);
	IFDEL(ReceiveBuffer);
	// KeepBuffer is stored within ReceiveBuffer, so we actually deleted it above, so there is no memory leaks!
}

MaxsiError MaxsiHTTP::Continue()
{
	// Check if this HTTP version is supported.
	if ( (Major != 1 && Minor != 1) ) { return MAXSI_ERROR_NOT_SUPPORTED; }

	if ( ReceiveBuffer == NULL || KeepBuffer == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	// Reset the interrupt value, so we don't cause another interrupt when we shouldn't.
	InterruptType		=	MAXSI_HTTP_INTERRUPT_NONE;
	InterruptData		=	NULL;

	// If the request method has not been specified, just do a GET.
	if ( Result == MAXSI_ERROR_SUCCESS && RequestMethod == NULL ) { Result = SetMethod((ASCII_t*)"GET"); }
	if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }

	ShouldMainloop	=	true;

	while ( ShouldMainloop && Result == MAXSI_ERROR_SUCCESS )
	{
		if ( Result == MAXSI_ERROR_SUCCESS && State == MAXSI_HTTP_STATE_CONNECTING ) { Result = Connect(); }
		if ( Result == MAXSI_ERROR_SUCCESS && State == MAXSI_HTTP_STATE_BUILDING_REQUEST) { Result = BuildRequest(); }
		if ( Result == MAXSI_ERROR_SUCCESS && State == MAXSI_HTTP_STATE_SENDING_REQUEST ) { Result = SendRequest(); }
		if ( Result == MAXSI_ERROR_SUCCESS && State > MAXSI_HTTP_STATE_SENDING_REQUEST ) { Result = ReadResponse(); /* State should not be increased here. */ }
		if ( Result == MAXSI_ERROR_SUCCESS && State == MAXSI_HTTP_STATE_DONE ) { ShouldMainloop = false; }
	}

	if ( Result != MAXSI_ERROR_INTERRUPT && Stream != NULL ) { Disconnect(); }

	if ( Result == MAXSI_ERROR_INTERRUPT ) { PrintOutput("Got an interrupt!\n"); }

	return Result;
}

MaxsiError MaxsiHTTP::Connect()
{
	// Check if we have a network, otherwise just pick the default one.
	if ( Network == NULL ) { Network = (ITCP*)CreateInterface("ITCP"); }
	if ( Network == NULL ) { return MAXSI_ERROR_INTERFACE_NOT_IMPLEMENTED; }

	// Connect to the remote host.
	Stream = Network->Connect(HostM, Port);

	// Check for error conditions.
	if ( Stream == NULL ) { return MAXSI_ERROR_COULD_NOT_CONNECT; }

	State++;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::Disconnect()
{
	// Disconnect from the server.
	Network->CloseConnection(Stream);

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::BuildRequest()
{
	if ( RequestMethod == NULL || Resource == NULL || HostA == NULL ) { return MAXSI_ERROR_NOT_INITIALIZED; }

	IFDEL(RequestHeaders);

	ASCII_t* MIMEBodyAttachmentHeaders		=	NULL;

	if ( MIMEBodyAttachment != NULL )
	{
		MIMEBodyAttachmentHeaders	=	(ASCII_t*)TestPrintStringA( 
			"Content-type: multipart/form-data; boundary=AaB03x\r\n"
			"Content-Length: %z\r\n",
			MIMEBodyAttachmentSize);
	}

	RequestHeaders		=	(ASCII_t*)BuildStringA(9,
		RequestMethod, " ",	Resource, " HTTP/1.1\r\n",
		"Host: ", HostA, "\r\n",
		MIMEBodyAttachmentHeaders,
		"\r\n");

	IFDEL(MIMEBodyAttachmentHeaders);

	if ( RequestHeaders == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	State++;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::SendRequest()
{
	size_t	RequestSize		=	strlen((char*)RequestHeaders);

	MaxsiError Result;

	if ( (Result = Stream->Write((BYTE*)RequestHeaders, RequestSize) != RequestSize) != MAXSI_ERROR_SUCCESS ) { return Result; }

	if ( MIMEBodyAttachment != NULL )
	{
		if ( (Result = Stream->Write(MIMEBodyAttachment, MIMEBodyAttachmentSize) != RequestSize) != MAXSI_ERROR_SUCCESS ) { return Result; }
	}

	State++;

	LastKeep	=	0;
	Keep		=	0;
	Read		=	0;

	return Result;
}

MaxsiError MaxsiHTTP::ReadResponse()
{
	while ( State == MAXSI_HTTP_STATE_STATUS_LINE || State == MAXSI_HTTP_STATE_HEADERS || State == MAXSI_HTTP_STATE_BODY )
	{
		// Read on our socket, if don't have unread data.
		if ( Read == 0 )
		{
			MaxsiError Result = Stream->Read(ReceiveBuffer+LastKeep, MAXSI_HTTP_RECEIVE_BUFFER_SIZE-Keep-1, &Read);

			if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }

			ReceiveBuffer[LastKeep+Read]			=	0;
			ReceiveBuffer[ReceiveBufferSize - 1]	=	0;
		}		

		// Remember to reset keep every time!
		Keep	=	0;

		MaxsiError	ParseStatus		=	ReadResponseHeaders(ReceiveBuffer, LastKeep+Read, &Keep);

		if ( Keep )
		{
			memcpy(ReceiveBuffer, ReceiveBuffer+LastKeep+Read-Keep, Keep);
			ReceiveBuffer[Keep] = 0;
		}

		LastKeep	=	Keep;

		// If something happened, such as an interrupt, then remember our location and return, otherwise reset our location.
		if ( ParseStatus != MAXSI_ERROR_SUCCESS ) { return ParseStatus; } else { Read = 0; }
	}

	return MAXSI_ERROR_SUCCESS;
}


MaxsiError MaxsiHTTP::ReadResponseHeaders(BYTE* Buffer, size_t Available, size_t* Keep)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	size_t Read		=	0;

	if ( State == MAXSI_HTTP_STATE_STATUS_LINE )
	{
		ResponseRead				=	0;
		ResponseSize				=	UINT64_MAX;
		ContentLength				=	UINT64_MAX;
#ifdef Maxsi_HTTP_Enable_Entity_Length
		EntityLength				=	UINT64_MAX;
#endif
		TransferEncoding			=	MAXSI_HTTP_TRANSFER_ENCODING_IDENTITY;

		while ( true )
		{
			// See if we have the status line yet.
			size_t	NextCR		=	FindCharA((char*)Buffer+Read, '\r', Available-Read);
			size_t	NextLF		=	FindCharA((char*)Buffer+Read, '\n', Available-Read);

			if ( NextCR == SIZE_MAX || NextLF == SIZE_MAX ) { *Keep = Available-Read; break; }

			// Check if the server accidentally sent empty lines before the actual status line,
			// which RFC 2616 4.1 tells us that we SHOULD. (Though that only applies to servers)
			if ( NextCR == 0 && NextLF == 1 ) { Read += NextLF + 1; continue; }

			// Read the actual status line.
			size_t FirstSpace	=	FindCharA((char*)Buffer+Read, ' ', NextCR);

			if ( FirstSpace == SIZE_MAX ) { return MAXSI_ERROR_RECEIVED_INVALID_INPUT; }

			Buffer[FirstSpace]	=	0;

			// Check if this HTTP version is supported.
			if ( strcmp((char*)Buffer+Read, "HTTP/1.1") != 0 ) { return MAXSI_ERROR_NOT_SUPPORTED; }

			// Read the response code
			ResponseCode	=	ReadHTTPResponseCode((char*)Buffer + Read + FirstSpace + 1);

			if ( ResponseCode == 0 ) { return MAXSI_ERROR_RECEIVED_INVALID_INPUT; }

			MAXSI_TODO("Use the Response Code in accordance with RFC 2616 sec 4.3 to filter out some codes that MUST NOT have a body, as required by RFC 2616 sec 4.4 1)");

			// Skip the rest of the line.
			Read += NextLF + 1;
			State++;

			break;
		}
	}

	if ( State == MAXSI_HTTP_STATE_HEADERS )
	{
		while ( true )
		{
			// See if we have any headers ready to be read.
			size_t	NextCR		=	FindCharA((char*)Buffer+Read, '\r', Available-Read);
			size_t	NextLF		=	FindCharA((char*)Buffer+Read, '\n', Available-Read);

			if ( NextCR == SIZE_MAX || NextLF == SIZE_MAX ) { *Keep = Available-Read; break; }

			// An empty line means the body is about to appear!
			if ( NextCR == 0 && NextLF == 1 ) { Read += NextLF + 1; State++; break; }

			// If a header line starts with a space or horizontal tab, then it is a continuation of the previous line. (RFC 2616 sec 4.2)
			// However, we don't support that yet, so let's just ignore these lines.
			if ( ((char*)Buffer+Read)[0] != ' ' || ((char*)Buffer+Read)[0] != '\t' )
			{
				size_t	NextColon	=	FindCharA((char*)Buffer+Read, ':', Available-Read);
				
				// A header line is required by RFC 2616 sec 4.2 to contain a : (unless it is a continued header line)
				if ( NextColon == SIZE_MAX ) { return MAXSI_ERROR_RECEIVED_INVALID_INPUT; }

				// A message header is required to contain a field-name that is a token
				// (RFC 2616 sec 2.2) token = 1*<any CHAR except CTLs or separators>
				if ( NextColon == 0 ) { return MAXSI_ERROR_RECEIVED_INVALID_INPUT; }

				// Split the string.
				((char*)Buffer)[Read + NextColon] = 0;
				((char*)Buffer)[Read + NextCR] = 0;

				// Skip any whitespace after the colon.
				size_t NextNonWS	=	SkipWhiteSpaceA(((char*)Buffer + Read + NextColon + 1), NextCR - NextColon - 1);

				// Handle the header by passing it the field-name and the field-value. (file-name ":" field-value CRLF)
				Result = HandleHeader(((char*)Buffer + Read), ((char*)Buffer + Read + NextColon + 1 + ((NextNonWS != SIZE_MAX) ? NextNonWS : 0)));

				if ( Result != MAXSI_ERROR_SUCCESS) { return Result; }
			}
			else
			{
				MAXSI_TODO("RFC 2616 mentions this is allowed, but doesn't specify exactly how it works, ");
			}

			Read += NextLF + 1;
		}

		// Determine the length of the response. (Content-Length is only used if no transfer encoding is in use)
		if ( TransferEncoding == MAXSI_HTTP_TRANSFER_ENCODING_IDENTITY )
		{
			// If a content-length is specified use it, otherwise, fallback on Entity-Length. This is non-standard
			// behavior, though, but can be useful.
			if ( ContentLength != UINT64_MAX )
			{
				ResponseSize = ContentLength;
			}
#ifdef Maxsi_HTTP_Enable_Entity_Length
			else
			{
				ResponseSize = EntityLength;
			}
#endif
		}
		else
		{
#ifdef Maxsi_HTTP_Enable_Entity_Length
			ResponseSize = EntityLength;
#endif
		}
	}

	if ( State == MAXSI_HTTP_STATE_BODY )
	{
		if ( ExpectingBody == false )
		{
			State++;
		}
		else
		{
			Result = ReadResponseBody(Buffer+Read, Available-Read, Keep);
		}
	}

	if ( State == MAXSI_HTTP_STATE_DONE )
	{
		// We have read all we need to! Next up, do something else, or close the connection!
		ShouldMainloop	=	false;
	}

	return Result;
}


MaxsiError MaxsiHTTP::HandleHeader(char* Header, char* Value)
{
	if ( strcasecmp(Header, "Transfer-Encoding") == 0 )
	{
		if ( strcasecmp(Value, "Identity") == 0 )
		{
			TransferEncoding	=	MAXSI_HTTP_TRANSFER_ENCODING_IDENTITY;
		}
		else if ( strcasecmp(Value, "Chunked") == 0 )
		{
			TransferEncoding	=	MAXSI_HTTP_TRANSFER_ENCODING_CHUNKED;
			// RFC 2616 sec 4.4 3) says we MUST ignore a content-length header if a transfer-encoding has been set.
			ContentLength		=	UINT64_MAX;
			ChunkedState		=	MAXSI_HTTP_CHUNKED_READING_CHUNK_SIZE;
		}
		else
		{
			return MAXSI_ERROR_NOT_SUPPORTED;
		}
	}
	else if ( strcasecmp(Header, "Content-Length") == 0 )
	{
		// RFC 2616 sec 4.4 3) says we MUST ignore a content-length header if a transfer-encoding has been set.
		if ( TransferEncoding == MAXSI_HTTP_TRANSFER_ENCODING_IDENTITY )
		{
			ContentLength		=	ReadUInt64A10(Value, strlen(Header));
		}	
	}
#ifdef Maxsi_HTTP_Enable_Entity_Length
	else if ( strcasecmp(Header, "Entity-Length") == 0 ) // The actual length of the transferred entity
	{
		EntityLength			=	ReadUInt64A10(Value, strlen(Header));
	}
#endif
	else if ( strcasecmp(Header, "Connection") == 0 )
	{
		if ( strcasecmp(Value, "Close") == 0 )
		{
			ShouldMainloop	=	false;
		}
		else if ( strcasecmp(Value, "Keep-Alive") == 0 )
		{
			ShouldMainloop	=	false;
		}
		else
		{
			return MAXSI_ERROR_NOT_SUPPORTED;
		}
	}
	else
	{
		//PrintOutput("Unknown or unimplemented HTTP header received:\n\t\%s: %s\n", Header, Value);
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::ReadResponseBody(BYTE* Buffer, size_t Available, size_t* Keep)
{
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	size_t Read = 0;

	if ( TransferEncoding == MAXSI_HTTP_TRANSFER_ENCODING_IDENTITY )
	{
		//ResponseRead += Available;

		size_t Accepted = 0;

		Result	=	OnActualData(UserData, Available, &Accepted);

		if ( Result != MAXSI_ERROR_SUCCESS ) { *Keep = Available-Read; return Result; }

		// If we have read it all, move on!
		//if ( ResponseRead == ResponseSize ) { State++; }

		return MAXSI_ERROR_SUCCESS;
	}
	else if ( TransferEncoding == MAXSI_HTTP_TRANSFER_ENCODING_CHUNKED ) 
	{
		while ( true )
		{
			if ( ChunkedState == MAXSI_HTTP_CHUNKED_READING_CHUNK_SIZE )
			{	
				// See if we have the chunk-size line yet.
				size_t	NextCR		=	FindCharA((char*)Buffer+Read, '\r', Available-Read);
				size_t	NextLF		=	FindCharA((char*)Buffer+Read, '\n', Available-Read);

				if ( NextCR == SIZE_MAX || NextLF == SIZE_MAX ) { *Keep = Available-Read; break; }

				// Check if any chunk-extensions are in use.
				size_t FirstSemiColon	=	FindCharA((char*)Buffer+Read, ';', NextCR);

				if ( FirstSemiColon != SIZE_MAX )
				{
					// Yes, we are using chunk-extensions!
			
					// RFC 2616 sec 3.6.1 says we MUST ignore any chunk-extension extensions we don't understand.
					((char*)Buffer + Read)[FirstSemiColon] = 0;
				}

				((char*)Buffer + Read)[NextCR] = 0;

				// Read the chunk-size field!
				char* 	ChunkSizeStr	=	(char*)(Buffer + Read);
				size_t	ChunkSizeLength	=	strlen(ChunkSizeStr);
				ChunkSize				=	ReadUInt64A16(ChunkSizeStr, ChunkSizeLength);
				ChunkRead				=	0;

				if ( ChunkSize == 0 )
				{
					// We have read the last chunk, so move on!
					State++;
					break;
				}	
			
				// Skip to the next line!
				Read += NextLF + 1;	
				ChunkedState++;
			}

			if ( Available-Read == 0 ) { break; }

			if ( ChunkedState == MAXSI_HTTP_CHUNKED_READING_CHUNK_DATA )
			{
				if ( ChunkRead < ChunkSize )
				{
					// Determine how much of the chunk is available.
					size_t ChunkAvailable = Available-Read;

					if ( ChunkAvailable > (ChunkSize-ChunkRead) ) { ChunkAvailable = (ChunkSize-ChunkRead); }

					size_t Accepted	= 0;

					// We will not handle the possible error situation right now.
					Result	=	OnActualData(Buffer + Read, ChunkAvailable, &Accepted);

					assert(Accepted <= Available);

					// Mark that we read it!
					//ResponseRead += Accepted;
					ChunkRead += Accepted;
					Read += Accepted;
				}

				if ( ChunkRead == ChunkSize )
				{
					// Now go to the next state!
					ChunkedState++;
				}
				
				// Handle the error situation here: Remember what's left and return.
				if ( Result != MAXSI_ERROR_SUCCESS ) { *Keep = Available-Read; return Result; }
			}

			if ( Available-Read == 0 ) { break; }

			if ( ChunkedState == MAXSI_HTTP_CHUNKED_READING_CHUNK_CRLF )
			{
				if ( Available-Read < 2 ) { *Keep = Available-Read; break; }

				if ( ((char*)Buffer + Read)[0] != '\r' || ((char*)Buffer + Read)[1] != '\n' )
				{
					// RFC 2616 sec 3.6.1 says we should receive a CRLF now, we didn't get that. Fail!
					return MAXSI_ERROR_RECEIVED_INVALID_INPUT;
				}
	
				// Now progress, or rather, reset!
				ChunkedState =  MAXSI_HTTP_CHUNKED_READING_CHUNK_SIZE;
				Read += 2;
			}

			if ( Available-Read == 0 ) { break; }
		}

		return MAXSI_ERROR_SUCCESS;
	}	
	else
	{
		return MAXSI_ERROR_NOT_IMPLEMENTED;	
	}
}

MaxsiError MaxsiHTTP::OnActualData(BYTE* Buffer, size_t Available, size_t* Accepted)
{
	if ( KeepBufferUsed == 0 )
	{
		// Since our Keep Buffer is empty, we can save time and effort by just passing along our input.
		size_t CallbackKeep = Callback(UserData, this, Buffer, Available);

		// Since we couldn't parse this in the first try, and it fills our entire buffer, we cannot ever furfill this. Fail.
		if ( CallbackKeep == KeepBufferSize ) { return MAXSI_ERROR_OUT_OF_MEM; }

		*Accepted = Available - CallbackKeep;

		if ( *Accepted == 0 )
		{
			KeepBufferUsed	=	CallbackKeep;
			memcpy(KeepBuffer, Buffer, Available);
		}
	}
	else
	{
		size_t KeepAvailable = (KeepBufferSize-KeepBufferUsed > Available) ? Available : KeepBufferSize-KeepBufferUsed;

		// Copy some of the new buffer into the kept buffer!
		memcpy(KeepBuffer + KeepBufferUsed, Buffer, KeepAvailable);

		size_t CallbackKeep = Callback(UserData, this, KeepBuffer, KeepBufferUsed + KeepAvailable);

		// See if our keep buffer is filled.
		if ( CallbackKeep == KeepBufferSize ) { return MAXSI_ERROR_OUT_OF_MEM; }

		if ( CallbackKeep == 0 )
		{
			KeepBufferUsed	=	0;
		}
		else
		{
			memcpy(KeepBuffer, KeepBuffer + KeepBufferUsed - CallbackKeep, CallbackKeep);
			KeepBufferUsed	=	KeepBufferUsed;
		}

		// Mark how much data we read.
		*Accepted = KeepAvailable;
	}

	// Check if the callback function issued an Interrupt. This makes the main loop exit, but all the
	// internal state information is kept. The main loop can then be continued by calling Continue()
	// from either this thread, or another.
	return ( InterruptType == MAXSI_HTTP_INTERRUPT_NONE ) ? MAXSI_ERROR_SUCCESS : MAXSI_ERROR_INTERRUPT;
}

void MaxsiHTTP::SetCallback(IHTTPCallback NewCallback)
{
	Callback	=	NewCallback;
}

void MaxsiHTTP::SetCallbackUserData(BYTE* NewUserData)
{
	UserData	=	NewUserData;
}

void MaxsiHTTP::SetNetwork(ITCP* NewNetwork)
{
	if ( Network != NULL ) { DeleteInterface(Network); }
	Network		=	NewNetwork;
}

void MaxsiHTTP::SetStream(IBufferStreamer* NewStream)
{
	Stream		=	NewStream;
}

MaxsiError MaxsiHTTP::SetMethod(ASCII_t* NewMethod)
{
	// RFC 2616 sec 9.4 says a HEAD request MUST NOT return a message body.
	ExpectingBody	=	( strcmp("HEAD", (char*)NewMethod) != 0 );

	MAXSI_TODO("Check if the new method is a string that conforms to RFC 2616 (Just alphabetical letters with no white space");

	ASCII_t*	NewMethodCopy	=	(ASCII_t*)BuildStringA(1, NewMethod);
	
	if ( NewMethodCopy == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	if ( RequestMethod != NULL ) { delete[] RequestMethod; }

	RequestMethod	=	NewMethodCopy;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::SetVersion(unsigned int NewMajor, unsigned int NewMinor)
{
	if ( (NewMajor != 1 && NewMinor != 1) ) { return MAXSI_ERROR_NOT_SUPPORTED; }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::SetURLA(char* NewURL)
{
	uint16_t	NewPort			=	80;
	ASCII_t*	NewHost			=	NULL;
	ASCII_t*	NewResource		=	NULL;

	MaxsiError	Result			=	ParseHTTPURL(NewURL, (char**)&NewHost, &NewPort, (char**)&NewResource);

	if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }
	
	if ( Result == MAXSI_ERROR_SUCCESS ) { Result = SetHost(NewHost); }
	if ( Result == MAXSI_ERROR_SUCCESS ) { Result = SetPort(NewPort); }
	if ( Result == MAXSI_ERROR_SUCCESS ) { Result = SetResource(NewResource); }

	delete[] NewHost;
	delete[] NewResource;

	return Result;
}

MaxsiError MaxsiHTTP::SetURLW(wchar_t* NewURL)
{
	char*	NewURLA		=	(char*)WCHAR2ASCII(NewURL);

	if ( NewURLA == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	MaxsiError	Result	=	SetURLA(NewURLA);

	delete[] NewURLA;

	return Result;
}

MaxsiError MaxsiHTTP::SetHost(ASCII_t* NewHost)
{
	MAXSI_TODO("Enforce that NewHost is a string that complies to RFC 2616 sec 3.1 (RFC 2396 sec 3.2.2)");

	ASCII_t*	NewHostACopy	=	(ASCII_t*)BuildStringA(1, NewHost);

	if ( NewHostACopy == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	MESTR* 		NewHostMCopy	=	ASCII2MESTR(NewHost);

	if ( NewHostMCopy == NULL ) { delete[] NewHostACopy; return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	if ( HostA != NULL ) { delete[] HostA; }
	if ( HostM != NULL ) { delete[] HostM; }
	
	HostA	=	NewHostACopy;
	HostM	=	NewHostMCopy;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::SetPort(uint16_t NewPort)
{
	Port	=	NewPort;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::SetResource(ASCII_t* NewResource)
{
	MAXSI_TODO("Enforce that NewResource is a string that complies to RFC 2616 sec 3.2.2!");

	ASCII_t* NewResourceCopy	=	(ASCII_t*)BuildStringA(1, NewResource);

	if ( NewResourceCopy == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	if ( Resource != NULL ) { delete[] Resource; }
	
	Resource	=	NewResourceCopy;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiHTTP::SetMimeAttachmentBody(BYTE* Buffer, size_t BufferSize)
{
	MIMEBodyAttachment		=	Buffer;
	MIMEBodyAttachmentSize	=	BufferSize;

	return MAXSI_ERROR_SUCCESS;
}

bool MaxsiHTTP::CompareURLsA(char* URL1, char* URL2)
{
	MAXSI_TODO("When comparing two URIs to decide if they match or not, a client SHOULD use a case-sensitive octet-by-octet comparison of the entire URIs, with these exceptions:"

      "- A port that is empty or not given is equivalent to the default port for that URI-reference;"

        "- Comparisons of host names MUST be case-insensitive;"
        "- Comparisons of scheme names MUST be case-insensitive;"
        "- An empty abs_path is equivalent to an abs_path of \"/\"."

	"Characters other than those in the \"reserved\" and \"unsafe\" sets (see RFC 2396 [42]) are equivalent to their \"\"%\" HEX HEX\" encoding.");

	return false;	
}

bool MaxsiHTTP::CompareURLsW(wchar_t* URL1, wchar_t* URL2)
{
	MAXSI_TODO("When comparing two URIs to decide if they match or not, a client SHOULD use a case-sensitive octet-by-octet comparison of the entire URIs, with these exceptions:"

      "- A port that is empty or not given is equivalent to the default port for that URI-reference;"

        "- Comparisons of host names MUST be case-insensitive;"
        "- Comparisons of scheme names MUST be case-insensitive;"
        "- An empty abs_path is equivalent to an abs_path of \"/\"."

	"Characters other than those in the \"reserved\" and \"unsafe\" sets (see RFC 2396 [42]) are equivalent to their \"\"%\" HEX HEX\" encoding.");

	return false;	
}


uint64_t MaxsiHTTP::GetResponseSize()
{
	return ResponseSize;
}

uint64_t MaxsiHTTP::GetResponseRead()
{
	return ResponseRead;	
}

void MaxsiHTTP::Interrupt(int Type, BYTE* UserData)
{
	InterruptType	=	Type;
	InterruptData	=	UserData;
}

int MaxsiHTTP::GetInterruptType()
{
	return InterruptType;
}

BYTE* MaxsiHTTP::GetInterruptData()
{
	return InterruptData;
}

EndMaxsiNamespace

